
/*
 * To match the U-Boot user interface on ARM platforms to the U-Boot
 * standard (as on PPC platforms), some messages with debug character
 * are removed from the default U-Boot build.
 *
 * Define DEBUG here if you want additional info as shown below
 * printed upon startup:
 *
 * U-Boot code: 00F00000 -> 00F3C774  BSS: -> 00FC3274
 * IRQ Stack: 00ebff7c
 * FIQ Stack: 00ebef7c
 */

#include <common.h>
#include <command.h>
#include <malloc.h>
#include <devices.h>
#include <version.h>
#include <net.h>

#ifdef CONFIG_DRIVER_SMC91111
#include "../drivers/smc91111.h"
#endif
#ifdef CONFIG_DRIVER_LAN91C96
#include "../drivers/lan91c96.h"
#endif

DECLARE_GLOBAL_DATA_PTR;

#if defined(CONFIG_CMD_NAND)
void nand_init (void);
#endif

#if defined(CONFIG_CMD_ONENAND)
void onenand_init(void);
#endif

ulong monitor_flash_len;

#ifdef CONFIG_HAS_DATAFLASH
extern int  AT91F_DataflashInit(void);
extern void dataflash_print_info(void);
#endif

#ifndef CONFIG_IDENT_STRING
#define CONFIG_IDENT_STRING ""
#endif

const char version_string[] =
	U_BOOT_VERSION" (" __DATE__ " - " __TIME__ ")"CONFIG_IDENT_STRING;

#ifdef CONFIG_DRIVER_CS8900
extern void cs8900_get_enetaddr (uchar * addr);
#endif

#ifdef CONFIG_DRIVER_RTL8019
extern void rtl8019_get_enetaddr (uchar * addr);
#endif

typedef volatile u8	S3C24X0_REG8;
typedef volatile u16	S3C24X0_REG16;
typedef volatile u32	S3C24X0_REG32;

/* I/O PORT (see manual chapter 9) */
typedef struct {
	S3C24X0_REG32	GPACON;
	S3C24X0_REG32	GPADAT;
	S3C24X0_REG32	res1[2];
	S3C24X0_REG32	GPBCON;
	S3C24X0_REG32	GPBDAT;
	S3C24X0_REG32	GPBUP;
	S3C24X0_REG32	res2;
	S3C24X0_REG32	GPCCON;
	S3C24X0_REG32	GPCDAT;
	S3C24X0_REG32	GPCUP;
	S3C24X0_REG32	res3;
	S3C24X0_REG32	GPDCON;
	S3C24X0_REG32	GPDDAT;
	S3C24X0_REG32	GPDUP;
	S3C24X0_REG32	res4;
	S3C24X0_REG32	GPECON;
	S3C24X0_REG32	GPEDAT;
	S3C24X0_REG32	GPEUP;
	S3C24X0_REG32	res5;
	S3C24X0_REG32	GPFCON;
	S3C24X0_REG32	GPFDAT;
	S3C24X0_REG32	GPFUP;
	S3C24X0_REG32	res6;
	S3C24X0_REG32	GPGCON;
	S3C24X0_REG32	GPGDAT;
	S3C24X0_REG32	GPGUP;
	S3C24X0_REG32	res7;
	S3C24X0_REG32	GPHCON;
	S3C24X0_REG32	GPHDAT;
	S3C24X0_REG32	GPHUP;
	S3C24X0_REG32	res8;

	S3C24X0_REG32	MISCCR;
	S3C24X0_REG32	DCLKCON;
	S3C24X0_REG32	EXTINT0;
	S3C24X0_REG32	EXTINT1;
	S3C24X0_REG32	EXTINT2;
	S3C24X0_REG32	EINTFLT0;
	S3C24X0_REG32	EINTFLT1;
	S3C24X0_REG32	EINTFLT2;
	S3C24X0_REG32	EINTFLT3;
	S3C24X0_REG32	EINTMASK;
	S3C24X0_REG32	EINTPEND;
	S3C24X0_REG32	GSTATUS0;
	S3C24X0_REG32	GSTATUS1;
	S3C24X0_REG32	GSTATUS2;
	S3C24X0_REG32	GSTATUS3;
	S3C24X0_REG32	GSTATUS4;

	S3C24X0_REG32	res9;
	S3C24X0_REG32	DSC0;
	S3C24X0_REG32	DSC1;
	S3C24X0_REG32	MSLCON;
	S3C24X0_REG32	GPJCON;
	S3C24X0_REG32	GPJDAT;
	S3C24X0_REG32	GPJUP;
	S3C24X0_REG32	res10;
} /*__attribute__((__packed__))*/ S3C24X0_GPIO;

typedef struct {
	S3C24X0_REG32	LOCKTIME;
	S3C24X0_REG32	MPLLCON;
	S3C24X0_REG32	UPLLCON;
	S3C24X0_REG32	CLKCON;
	S3C24X0_REG32	CLKSLOW;
	S3C24X0_REG32	CLKDIVN;
#ifdef CONFIG_S3C2440
	S3C24X0_REG32	CAMDIVN;
#endif
} /*__attribute__((__packed__))*/ S3C24X0_CLOCK_POWER;

/* PWM TIMER (see manual chapter 10) */
typedef struct {
	S3C24X0_REG32	TCNTB;
	S3C24X0_REG32	TCMPB;
	S3C24X0_REG32	TCNTO;
} /*__attribute__((__packed__))*/ S3C24X0_TIMER;

typedef struct {
	S3C24X0_REG32	TCFG0;
	S3C24X0_REG32	TCFG1;
	S3C24X0_REG32	TCON;
	S3C24X0_TIMER	ch[4];
	S3C24X0_REG32	TCNTB4;
	S3C24X0_REG32	TCNTO4;
} /*__attribute__((__packed__))*/ S3C24X0_TIMERS;


/* S3C2440 device base addresses */
#define S3C24X0_MEMCTL_BASE		0x48000000
#define S3C24X0_USB_HOST_BASE		0x49000000
#define S3C24X0_INTERRUPT_BASE		0x4A000000
#define S3C24X0_DMA_BASE		0x4B000000
#define S3C24X0_CLOCK_POWER_BASE	0x4C000000
#define S3C24X0_LCD_BASE		0x4D000000
#define S3C2440_NAND_BASE		0x4E000000
#define S3C24X0_UART_BASE		0x50000000
#define S3C24X0_TIMER_BASE		0x51000000
#define S3C24X0_USB_DEVICE_BASE		0x52000140
#define USB_DEVICE_PHYS_ADR		0x52000000
#define S3C24X0_WATCHDOG_BASE		0x53000000
#define S3C24X0_I2C_BASE		0x54000000
#define S3C24X0_I2S_BASE		0x55000000
#define S3C24X0_GPIO_BASE		0x56000000
#define S3C24X0_RTC_BASE		0x57000000
#define S3C2440_ADC_BASE		0x58000000
#define S3C24X0_SPI_BASE		0x59000000
#define S3C2440_SDI_BASE		0x5A000000

#define oNFCONF			0x00
#define oNFCONT			0x04


#define C1_MMU		(1<<0)		/* mmu off/on */
#define C1_ALIGN	(1<<1)		/* alignment faults off/on */
#define C1_DC		(1<<2)		/* dcache off/on */

#define C1_BIG_ENDIAN	(1<<7)		/* big endian off/on */
#define C1_SYS_PROT	(1<<8)		/* system protection */
#define C1_ROM_PROT	(1<<9)		/* ROM protection */
#define C1_IC		(1<<12)		/* icache off/on */
#define C1_HIGH_VECTORS	(1<<13)		/* location of vectors: low/high addresses */


static inline S3C24X0_CLOCK_POWER * S3C24X0_GetBase_CLOCK_POWER(void)
{
	return (S3C24X0_CLOCK_POWER * const)S3C24X0_CLOCK_POWER_BASE;
}



static inline S3C24X0_GPIO * S3C24X0_GetBase_GPIO(void)
{
	return (S3C24X0_GPIO * const)S3C24X0_GPIO_BASE;
}

static inline S3C24X0_TIMERS * S3C24X0_GetBase_TIMERS(void)
{
	return (S3C24X0_TIMERS * const)S3C24X0_TIMER_BASE;
}

#define MPLL 0
#define UPLL 1


static ulong get_PLLCLK(int pllreg)
{
    S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER();
    ulong r, m, p, s;

    if (pllreg == MPLL)
	r = clk_power->MPLLCON;
    else if (pllreg == UPLL)
	r = clk_power->UPLLCON;
    else
	hang();

    m = ((r & 0xFF000) >> 12) + 8;
    p = ((r & 0x003F0) >> 4) + 2;
    s = r & 0x3;
    /* To avoid integer overflow, changed the calc order */
    if (pllreg == MPLL)
    	return ( 2 * m * (CONFIG_SYS_CLK_FREQ / (p << s )) );
    else
    	return ( m * (CONFIG_SYS_CLK_FREQ / (p << s )) );
}


/* return FCLK frequency */
ulong get_FCLK(void)
{
    return(get_PLLCLK(MPLL));
}

/* return HCLK frequency */
ulong get_HCLK(void)
{
    S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER();

    switch (clk_power->CLKDIVN & 0x6) {
        case 0x0:
	    return get_FCLK();
	case 0x2:
	    return get_FCLK()/2;
	case 0x4:
	    return (clk_power->CAMDIVN & 0x200) ? get_FCLK()/8 : get_FCLK()/4;
	case 0x6:
	    return (clk_power->CAMDIVN & 0x100) ? get_FCLK()/6 : get_FCLK()/3;
    }
    return 0;
}

ulong get_PCLK(void)
{
    S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER();

    return((clk_power->CLKDIVN & 0x1) ? get_HCLK()/2 : get_HCLK());
}

static void cp_delay (void)
{
	volatile int i;

	/* copro seems to need some delay between reading and writing */
	for (i = 0; i < 100; i++);
}

/* read co-processor 15, register #1 (control register) */
static unsigned long read_p15_c1 (void)
{
	unsigned long value;

	__asm__ __volatile__(
		"mrc	p15, 0, %0, c1, c0, 0   @ read control reg\n"
		: "=r" (value)
		:
		: "memory");

	return value;
}


/* write to co-processor 15, register #1 (control register) */
static void write_p15_c1 (unsigned long value)
{
	__asm__ __volatile__(
		"mcr	p15, 0, %0, c1, c0, 0   @ write it back\n"
		:
		: "r" (value)
		: "memory");

	read_p15_c1 ();
}


void icache_enable (void)
{
	ulong reg;

	reg = read_p15_c1 ();		/* get control reg. */
	cp_delay ();
	write_p15_c1 (reg | C1_IC);
}


void dcache_enable (void)
{
	ulong reg;

	reg = read_p15_c1 ();
	cp_delay ();
	write_p15_c1 (reg | C1_DC);
}


/*
 * Begin and End of memory area for malloc(), and current "brk"
 */
static ulong mem_malloc_start = 0;
static ulong mem_malloc_end = 0;
static ulong mem_malloc_brk = 0;

static
void mem_malloc_init (ulong dest_addr)
{
	mem_malloc_start = dest_addr;
	mem_malloc_end = dest_addr + CFG_MALLOC_LEN;
	mem_malloc_brk = mem_malloc_start;

	memset ((void *) mem_malloc_start, 0,
			mem_malloc_end - mem_malloc_start);
}

void *sbrk (ptrdiff_t increment)
{
	ulong old = mem_malloc_brk;
	ulong new = old + increment;

	if ((new < mem_malloc_start) || (new > mem_malloc_end)) {
		return (NULL);
	}
	mem_malloc_brk = new;

	return ((void *) old);
}



/************************************************************************
 * Init Utilities							*
 ************************************************************************
 * Some of this code should be moved into the core functions,
 * or dropped completely,
 * but let's get it working (again) first...
 */

static int init_baudrate (void)
{
	gd->bd->bi_baudrate = gd->baudrate =115200;

	return (0);
}

static int display_banner (void)
{
	serial_putc('A');
	serial_putc('B');
	serial_putc('C');
	return (0);
}

int timer_load_val = 0;
static ulong timestamp;
static ulong lastdec;


int interrupt_init (void)
{
	S3C24X0_TIMERS * const timers = S3C24X0_GetBase_TIMERS();

	/* use PWM Timer 4 because it has no output */
	/* prescaler for Timer 4 is 16 */
	timers->TCFG0 = 0x0f00;
	timer_load_val = get_PCLK()/(2 * 16 * 100);
	/* load value for 10 ms timeout */
	lastdec = timers->TCNTB4 = timer_load_val;
	/* auto load, manual update of Timer 4 */
	timers->TCON = (timers->TCON & ~0x0700000) | 0x600000;
	/* auto load, start Timer 4 */
	timers->TCON = (timers->TCON & ~0x0700000) | 0x500000;
	timestamp = 0;

	return (0);
}

/*
 * WARNING: this code looks "cleaner" than the PowerPC version, but
 * has the disadvantage that you either get nothing, or everything.
 * On PowerPC, you might see "DRAM: " before the system hangs - which
 * gives a simple yet clear indication which part of the
 * initialization if failing.
 */
static int display_dram_config (void)
{
	int i;

#ifdef DEBUG
	puts ("RAM Configuration:\n");

	for(i=0; i<CONFIG_NR_DRAM_BANKS; i++) {
		printf ("Bank #%d: %08lx ", i, gd->bd->bi_dram[i].start);
		print_size (gd->bd->bi_dram[i].size, "\n");
	}
#else
	ulong size = 0;

	for (i=0; i<CONFIG_NR_DRAM_BANKS; i++) {
		size += gd->bd->bi_dram[i].size;
	}
	puts("DRAM:  ");
	print_size(size, "\n");
#endif

	return (0);
}

#ifndef CFG_NO_FLASH
static void display_flash_config (ulong size)
{
	puts ("Flash: ");
	print_size (size, "\n");
}
#endif /* CFG_NO_FLASH */

/*
 * Breathe some life into the board...
 *
 * Initialize a serial port as console, and carry out some hardware
 * tests.
 *
 * The first part of initialization is running from Flash memory;
 * its main purpose is to initialize the RAM so that we
 * can relocate the monitor code to RAM.
 */

/*
 * All attempts to come up with a "common" initialization sequence
 * that works for all boards and architectures failed: some of the
 * requirements are just _too_ different. To get rid of the resulting
 * mess of board dependent #ifdef'ed code we now make the whole
 * initialization sequence configurable to the user.
 *
 * The requirements for any new initalization function is simple: it
 * receives a pointer to the "global data" structure as it's only
 * argument, and returns an integer return code, where 0 means
 * "continue" and != 0 means "fatal error, hang the system".
 */
typedef int (init_fnc_t) (void);

int print_cpuinfo (void); /* test-only */


int cpu_init (void)
{
	/*
	 * setup up stacks if necessary
	 */
#ifdef CONFIG_USE_IRQ
	IRQ_STACK_START = _armboot_start - CFG_MALLOC_LEN - CFG_GBL_DATA_SIZE - 4;
	FIQ_STACK_START = IRQ_STACK_START - CONFIG_STACKSIZE_IRQ;
#endif
	return 0;
}


int board_init (void)
{
	S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER();
	S3C24X0_GPIO * const gpio = S3C24X0_GetBase_GPIO();

	/* to reduce PLL lock time, adjust the LOCKTIME register */
//	clk_power->LOCKTIME = 0xFFFFFF;

	/* configure MPLL */
//	clk_power->MPLLCON = ((M_MDIV << 12) + (M_PDIV << 4) + M_SDIV);

	/* some delay between MPLL and UPLL */
//	delay (4000);

	/* configure UPLL */
//	clk_power->UPLLCON = ((U_M_MDIV << 12) + (U_M_PDIV << 4) + U_M_SDIV);

	/* some delay between MPLL and UPLL */
//	delay (8000);

	/* set up the I/O ports */
	gpio->GPACON = 0x007FFFFF;
	gpio->GPBCON = 0x002a9655;
	gpio->GPBUP = 0x000007FF;
	gpio->GPCCON = 0xAAAAAAAA;
	gpio->GPCUP = 0x0000FFFF;
	gpio->GPDCON = 0xAAAAAAAA;
	gpio->GPDUP = 0x0000FFFF;
	gpio->GPECON = 0xAAAAAAAA;
	gpio->GPEUP = 0x0000FFFF;
	gpio->GPFCON = 0x000055AA;
	gpio->GPFUP = 0x000000FF;
	gpio->GPGCON = 0xFD95FFBA;
	gpio->GPGUP = 0x0000FFFF;
#ifdef CONFIG_SERIAL3
	gpio->GPHCON = 0x002AAAAA;
#else
	gpio->GPHCON = 0x002AFAAA;
#endif
	gpio->GPHUP = 0x000007FF;

	gpio->GPJCON = 0x2AAAAAA;

#if 0
	/* USB Device Part */
	/*GPGCON is reset for USB Device */
	gpio->GPGCON = (gpio->GPGCON & ~(3 << 24)) | (1 << 24); /* Output Mode */
	gpio->GPGUP = gpio->GPGUP | ( 1 << 12);			/* Pull up disable */

	gpio->GPGDAT |= ( 1 << 12) ; 
	gpio->GPGDAT &= ~( 1 << 12) ; 
	udelay(20000);
	gpio->GPGDAT |= ( 1 << 12) ; 
#endif

	/* arch number of SMDK2440-Board */
	gd->bd->bi_arch_number = MACH_TYPE_S3C2440;

	/* adress of boot parameters */
	gd->bd->bi_boot_params = 0x30000100;

	icache_enable();
	dcache_enable();

	return 0;
}

init_fnc_t *init_sequence[] = {
	cpu_init,		/* basic cpu dependent setup */
	board_init,		/* basic board dependent setup */
	interrupt_init,		/* set up exceptions */
	//env_init,		/* initialize environment */
	init_baudrate,		/* initialze baudrate settings */
	serial_init,		/* serial communications setup */
	//console_init_f,		/* stage 1 init of console */
	display_banner,		/* say that we are here */
#if defined(CONFIG_DISPLAY_CPUINFO)
	//print_cpuinfo,		/* display cpu info (and speed) */
#endif
#if defined(CONFIG_DISPLAY_BOARDINFO)
	//checkboard,		/* display board info */
#endif
	//dram_init,		/* configure available RAM banks */
	//display_dram_config,
	//eth_init,		/* weibing */
	NULL,
};

void * memset(void * s,int c,size_t count)
{
	char *xs = (char *) s;
	while (count--)
		*xs++ = c;
	return s;
}

void start_armboot (void)
{
	init_fnc_t **init_fnc_ptr;
	char *s;
#ifndef CFG_NO_FLASH
	ulong size;
#endif
#if defined(CONFIG_VFD) || defined(CONFIG_LCD)
	unsigned long addr;
#endif

	/* Pointer is writable since we allocated a register for it */
	gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));
	/* compiler optimization barrier needed for GCC >= 3.4 */
	__asm__ __volatile__("": : :"memory");

	memset ((void*)gd, 0, sizeof (gd_t));
	gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
	memset (gd->bd, 0, sizeof (bd_t));

	monitor_flash_len = _bss_start - _armboot_start;

	for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
		if ((*init_fnc_ptr)() != 0) {
			hang ();
		}
	}

	return;
}

void hang (void)
{
	for (;;);
}


/*--------------------------------------------------*/

void do_undefined_instruction (struct pt_regs *pt_regs)
{
}

void do_software_interrupt (struct pt_regs *pt_regs)
{
}

void do_prefetch_abort (struct pt_regs *pt_regs)
{
}

void do_data_abort (struct pt_regs *pt_regs)
{
}

void do_not_used (struct pt_regs *pt_regs)
{
}

void do_fiq (struct pt_regs *pt_regs)
{
}

void do_irq (struct pt_regs *pt_regs)
{
}


/*
 * Standard NAND flash commands
 */

#define	NF_BASE		0x4e000000

#define __REGw(x)	(*(volatile unsigned short *)(x))
#define __REGb(x)	(*(volatile unsigned char *)(x))
#define __REGi(x)	(*(volatile unsigned int *)(x))

#define NAND_CMD_READ0		0
#define NAND_CMD_READ1		1
#define NAND_CMD_PAGEPROG	0x10
#define NAND_CMD_READOOB	0x50
#define NAND_CMD_ERASE1		0x60
#define NAND_CMD_STATUS		0x70
#define NAND_CMD_STATUS_MULTI	0x71
#define NAND_CMD_SEQIN		0x80
#define NAND_CMD_READID		0x90
#define NAND_CMD_ERASE2		0xd0
#define NAND_CMD_RESET		0xff

#define NFCONF		__REGi(NF_BASE + 0x0)
#define NFCONT		__REGi(NF_BASE + 0x4)
#define NFCMD		__REGb(NF_BASE + 0x8)
#define NFADDR		__REGb(NF_BASE + 0xc)
#define NFDATA		__REGb(NF_BASE + 0x10)
#define NFDATA16	__REGw(NF_BASE + 0x10)
#define NFSTAT		__REGb(NF_BASE + 0x20)
#define NFSTAT_BUSY	1
#define nand_select()	(NFCONT &= ~(1 << 1))
#define nand_deselect()	(NFCONT |= (1 << 1))
#define nand_clear_RnB()	(NFSTAT |= (1 << 2))

#define NAND_PAGE_SIZE		512
#define BAD_BLOCK_OFFSET	517
#define NAND_BLOCK_MASK		(NAND_PAGE_SIZE - 1)
#define NAND_BLOCK_SIZE		0x4000


static inline void nand_wait(void)
{
	int i;

	while (!(NFSTAT & NFSTAT_BUSY))
		for (i=0; i<10; i++);
}

static int is_bad_block(unsigned long i)
{
	unsigned char data;
	unsigned long page_num;

	/* FIXME: do this twice, for first and second page in block */

	nand_clear_RnB();
#if (NAND_PAGE_SIZE == 512)
	NFCMD = NAND_CMD_READOOB; /* 0x50 */
	NFADDR = BAD_BLOCK_OFFSET & 0xf;
	NFADDR = (i >> 9) & 0xff;
	NFADDR = (i >> 17) & 0xff;
	NFADDR = (i >> 25) & 0xff;
#elif (NAND_PAGE_SIZE == 2048)
	page_num = i >> 11; /* addr / 2048 */
	NFCMD = NAND_CMD_READ0;
	NFADDR = BAD_BLOCK_OFFSET & 0xff;
	NFADDR = (BAD_BLOCK_OFFSET >> 8) & 0xff;
	NFADDR = page_num & 0xff;
	NFADDR = (page_num >> 8) & 0xff;
	NFADDR = (page_num >> 16) & 0xff;
	NFCMD = NAND_CMD_READSTART;
#endif
	nand_wait();
	data = (NFDATA & 0xff);
	if (data != 0xff)
		return 1;

	return 0;
}



static int nand_read_page_ll(unsigned char *buf, unsigned long addr)
{
	unsigned short *ptr16 = (unsigned short *)buf;
	unsigned int i, page_num;

	nand_clear_RnB();

	NFCMD = NAND_CMD_READ0;

#if (NAND_PAGE_SIZE == 512)
	/* Write Address */
	NFADDR = addr & 0xff;
	NFADDR = (addr >> 9) & 0xff;
	NFADDR = (addr >> 17) & 0xff;
	NFADDR = (addr >> 25) & 0xff;
#elif (NAND_PAGE_SIZE == 2048)
	page_num = addr >> 11; /* addr / 2048 */
	/* Write Address */
	NFADDR = 0;
	NFADDR = 0;
	NFADDR = page_num & 0xff;
	NFADDR = (page_num >> 8) & 0xff;
	NFADDR = (page_num >> 16) & 0xff;
	NFCMD = NAND_CMD_READSTART;
#else
#error "unsupported nand page size"
#endif
	nand_wait();

//#if defined(CONFIG_S3C2410)
#if 0		//use 512b flash,weibing
	for (i = 0; i < NAND_PAGE_SIZE; i++) {
		*buf = (NFDATA & 0xff);
		buf++;
	}
#elif defined(CONFIG_S3C2440)
	for (i = 0; i < NAND_PAGE_SIZE/2; i++) {
		*ptr16 = NFDATA16;
		ptr16++;
	}
#endif

	return NAND_PAGE_SIZE;
}


int nand_read_ll(unsigned char *buf, unsigned long start_addr, int size)
{
	int i, j;

	if ((start_addr & NAND_BLOCK_MASK) || (size & NAND_BLOCK_MASK))
		return -1;	/* invalid alignment */

	/* chip Enable */
	nand_select();
	nand_clear_RnB();
	for (i=0; i<10; i++);

	for (i=start_addr; i < (start_addr + size);) {
#ifdef CONFIG_S3C2410_NAND_SKIP_BAD
		if (start_addr % NAND_BLOCK_SIZE == 0) {
			if (is_bad_block(i)) {
				/* Bad block */
				i += NAND_BLOCK_SIZE;
				size += NAND_BLOCK_SIZE;
				continue;
			}
		}
#endif
		j = nand_read_page_ll(buf, i);
		i += j;
		buf += j;
	//	LED_FLASH();
	}

	/* chip Disable */
	nand_deselect();

	return 0;
}
